﻿using System.Collections.Generic;
using System.Linq;
using Myotragus.Data.Criteria;
using Myotragus.Data.Domain;
using Myotragus.Data.Util;
using NHibernate;
using NHibernate.Criterion;

namespace Myotragus.Data.RepositoryExtensions
{
  public static class LocalizableReadRepositoryExtensions
  {
    public static TEntity Load<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object id)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return FindMatching(@this, lcId, new { Id = id }).First();
    }

    public static bool Exists<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object id)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return CountMatching(@this, lcId, new { Id = id }) == 1;
    }

    public static IEnumerable<TEntity> FindAll<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, params Order[] orderBy)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.FindAll(lcId, 0, -1, orderBy);
    }

    public static IEnumerable<TEntity> FindAll<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, int firstResult, int maxResults, params Order[] orderBy)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        var criteria = CriteriaHelper.CreateCriteria(session, 
            LocalizableCriteriaHelper.ResolveLoadEntityImplementation(@this), 
                                                     firstResult, maxResults, orderBy);

        PrepareCriteria(@this, criteria, lcId);

        return criteria.List().Cast<TEntity>();
      }
    }

    public static int CountAll<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        var criteria = session.CreateCriteria(LocalizableCriteriaHelper.ResolveLoadEntityImplementation(@this));

        criteria.SetProjection(Projections.RowCount());

        return criteria.UniqueResult<int>();
      }
    }

    public static IEnumerable<TEntity> FindMatching<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object pattern, params Order[] orderBy)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.FindMatching(lcId, pattern, 0, -1, orderBy);
    }

    public static IEnumerable<TEntity> FindMatching<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object pattern, int firstResult, int maxResults, params Order[] orderBy)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        var criteria = CriteriaHelper.CreateCriteria(session, LocalizableCriteriaHelper.ResolveLoadEntityImplementation(@this),
                                                     firstResult, maxResults, orderBy);

        PrepareCriteria(@this, criteria, lcId);
        LocalizableCriteriaHelper.AddMatchingCriteria(@this, criteria, pattern);

        return criteria.List().Cast<TEntity>();
      }
    }

    public static int CountMatching<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object pattern)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        var criteria = CriteriaHelper.CreateCriteria(session, LocalizableCriteriaHelper.ResolveLoadEntityImplementation(@this),
                                                     0, -1, null);

        PrepareCriteria(@this, criteria, lcId);
        LocalizableCriteriaHelper.AddMatchingCriteria(@this, criteria, pattern);
        criteria.SetProjection(Projections.RowCount());

        return criteria.UniqueResult<int>();
      }
    }

    public static IEnumerable<TEntity> FindLike<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object pattern, params Order[] orderBy)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.FindLike(lcId, pattern, 0, -1, orderBy);
    }

    public static IEnumerable<TEntity> FindLike<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object pattern, int firstResult, int maxResults, params Order[] orderBy)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        var criteria = CriteriaHelper.CreateCriteria(session, LocalizableCriteriaHelper.ResolveLoadEntityImplementation(@this),
                                                     firstResult, maxResults, orderBy);

        PrepareCriteria(@this, criteria, lcId);
        LocalizableCriteriaHelper.AddLikeCriteria(@this, criteria, pattern);

        return criteria.List().Cast<TEntity>();
      }
    }

    public static int CountLike<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      int lcId, object pattern)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        var criteria = CriteriaHelper.CreateCriteria(session, LocalizableCriteriaHelper.ResolveLoadEntityImplementation(@this),
                                                     0, -1, null);

        PrepareCriteria(@this, criteria, lcId);
        LocalizableCriteriaHelper.AddLikeCriteria(@this, criteria, pattern);
        criteria.SetProjection(Projections.RowCount());

        return criteria.UniqueResult<int>();
      }
    }

    private static void PrepareCriteria<TEntity, TDescriptor, TProjector, TAlias>(
      this ILocalizableReadRepository<TEntity, TProjector, TAlias> @this, 
      ICriteria criteria, int lcId)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> 
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      LocalizableCriteriaHelper.AddAliases(@this, criteria, lcId);
      LocalizableCriteriaHelper.SetProjection(@this, criteria, null);
      LocalizableCriteriaHelper.SetResultTransformer(@this, criteria);
    }
  }
}